#pragma once

#include <cstddef>
#include <functional>
#include <new>


namespace evm {

class ScopeGuardImplBase {
public:
    void dismiss() noexcept {
        dismissed_ = true;
    }

protected:
    ScopeGuardImplBase()
        : dismissed_(false) {}

    ScopeGuardImplBase(ScopeGuardImplBase&& other) noexcept
        : dismissed_(other.dismissed_) {
        other.dismissed_ = true;
    }

    bool dismissed_;
};

template <typename FunctionType>
class ScopeGuardImpl : public ScopeGuardImplBase {
public:
    explicit ScopeGuardImpl(const FunctionType& fn)
        : function_(fn) {}

    explicit ScopeGuardImpl(FunctionType&& fn)
        : function_(std::move(fn)) {}

    ScopeGuardImpl(ScopeGuardImpl&& other)
        : ScopeGuardImplBase(std::move(other))
        , function_(std::move(other.function_)) {
    }

    ~ScopeGuardImpl() noexcept {
        if (!dismissed_) {
            execute();
        }
    }

private:
    void* operator new(size_t) = delete;

    void execute() noexcept { function_(); }

    FunctionType function_;
};

template <typename FunctionType>
ScopeGuardImpl<typename std::decay<FunctionType>::type>
make_guard(FunctionType&& fn) {
    return ScopeGuardImpl<typename std::decay<FunctionType>::type>(
        std::forward<FunctionType>(fn));
}

/* This is largely unneeded if you just use auto for your guards. */
typedef ScopeGuardImplBase&& ScopeGuard;

/* ANONYMOUS_VARIABLE(str) introduces an identifier starting with
 * str and ending with a number that varies with the line. */
#define CONCATENATE_IMPL(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)
#endif

enum class ScopeGuardOnExit {};

template <typename FunctionType>
ScopeGuardImpl<typename std::decay<FunctionType>::type>
operator+(ScopeGuardOnExit, FunctionType&& fn) {
    return ScopeGuardImpl<typename std::decay<FunctionType>::type>(
        std::forward<FunctionType>(fn));
}

#define SCOPE_EXIT \
    auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
    = ScopeGuardOnExit() + [&]() noexcept

}
